/*
 * Copyright 2008 Andrej Herich
 * 
 * http://azetclient.yw.sk/
 * http://azetclient.googlecode.com/
 * 
 * This file is part of AzetClient.
 * 
 * AzetClient is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * AzetClient is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with AzetClient.  If not, see <http://www.gnu.org/licenses/>.
 */

package sk.yw.azetclient.azet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import sk.yw.azetclient.Main;
import sk.yw.azetclient.HtmlEntitiesConverter;
import sk.yw.azetclient.HttpURLHandler;
import sk.yw.azetclient.connection.AbstractParser;
import sk.yw.azetclient.connection.PagesSaver;
import sk.yw.azetclient.model.Buddy;

/**
 *
 * @author error216
 */
public class MessagesParser extends AbstractParser<List<AzetMessageBean>> {
    
    private static final Logger logger = Logger.getLogger(MessagesParser.class);
    
    private static final Pattern MESSAGE = Pattern.compile(
            "<tr>.*?<td><input type=\"checkbox\".*? value=\"(.*?)\">(.*?)</td>.*?"
            + "<th><a href=\".*?\">(.*?)</a></th>.*?"
            + "<td><a href=\".*?\">(.*?)</a></td>.*?</tr>");
    private static final DateFormat SEND_TIME_FORMAT =
            new SimpleDateFormat("yyyy-MM-dd HH:mm", Main.SLOVAK_LOCALE);
    
    private static final Pattern SIGN = Pattern.compile(
            "<input class=\"none\" type=\"hidden\" name=\"sign\" value=\"(.*?)\"");
    private static final Pattern SEND_TIME = Pattern.compile(
            "<input class=\"none\" type=\"hidden\" name=\"casPovod\" value=\"(.*?)\"");
    public static final DateFormat ADVANCED_SEND_TIME_FORMAT =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Main.SLOVAK_LOCALE);
    private static final Pattern CONTENT = Pattern.compile(
            "<input class=\"none\" type=\"hidden\" name=\"textPovod\" value=\"(.*?)\"");
    private static final Pattern SENDER_ID = Pattern.compile(
            "<input class=\"none\" type=\"hidden\" name=\"AZetIDUser\" value=\"(.*?)\"");
    private static final Pattern PREVIOUS_BEGIN = Pattern.compile(
            "<div.*?id=\"sndFrm\"");
    private static final Pattern PREVIOUS_MESSAGE = Pattern.compile(
            "<p.*?>Poslali ste \\D* (.*?)</p>.*?<p.*?>(.*?)</p>",
            Pattern.DOTALL);
    private static final DateFormat TIME_OF_PREVIOUS_FORMAT =
            new SimpleDateFormat("dd.MM.yyyy 'o' HH:mm", Main.SLOVAK_LOCALE);
    
    private Buddy user;
    private String messageUrl;
    private boolean advancedReceiving = false;
    
    private String getMessageUrl(AzetMessageBean message) {
        return messageUrl
                .replace("$MESSAGE_ID$", String.valueOf(message.getId()));
    }

    public List<AzetMessageBean> parse(InputStream in) throws IOException {
        List<AzetMessageBean> messages = new ArrayList<AzetMessageBean>();
        BufferedReader reader = new BufferedReader(new InputStreamReader(in,
                Main.getProperty("azetclient.azet.encoding")));
        PagesSaver saver = new PagesSaver("messages");
        
        String line;
        try {
            while ((line = reader.readLine()) != null) {
                saver.println(line);
                Matcher matcher = MESSAGE.matcher(line);
                while (matcher.find()) {
                    AzetMessageBean message = new AzetMessageBean();
                    message.setReceiver(user);
                    message.setSender(new Buddy());
                    String group;
                    
                    // id
                    group = matcher.group(1).trim();
                    try {
                        message.setId(Long.parseLong(group));
                    } catch (NumberFormatException ex) {
                        logger.warn("Invalid message id: " + group, ex);
                    }
                    
                    // send time
                    group = HtmlEntitiesConverter.convert(
                            matcher.group(2)).trim();
                    try {
                        message.setSendTime(SEND_TIME_FORMAT.parse(group));
                    } catch (ParseException ex) {
                        logger.warn("Invalid message send time: " + group, ex);
                    }
                    
                    // sender
                    group = matcher.group(3).trim();
                    message.getSender().setName(group);
                   
                    // content
                    group = HtmlEntitiesConverter.convert(
                            matcher.group(4)).trim();
                    message.setContent(group);
                    
                    if (advancedReceiving) {
                        this.doAdvancedReceive(message);
                    }
                    
                    messages.add(message);
                }
            }
        } finally {
            saver.close();
            reader.close();
        }
        
        return messages;
    }

    public void setUser(Buddy user) {
        this.user = user;
    }

    public void setMessageUrl(String messageUrl) {
        this.messageUrl = messageUrl;
    }
    
    public void setAdvancedReceiving(boolean advancedReceiving) {
        this.advancedReceiving = advancedReceiving;
    }

    /**
     * "Advanced receive" means to open the private message, not just read it
     * from the list of messages. This deletes the message from AZet, so the
     * operation can't be redone.
     * Warning: This operation can take some time. It is not advisable to call
     * it from event dispecher thread.
     * 
     * @param  message  message to receive; I/O parameter in fact
     */
    private void doAdvancedReceive(AzetMessageBean message)
            throws IOException {
        logger.debug("Attempting to do an advanced receive of message: " + message.getId());
        BufferedReader reader = null;
        PagesSaver saver = null;
        try {
            URL url = new URL(getMessageUrl(message));
            
            HttpURLHandler urlHandler = new HttpURLHandler(url);
            reader = new BufferedReader(new InputStreamReader(
                    urlHandler.getInputStream(),
                    Main.getProperty("azetclient.azet.encoding")));
            saver = new PagesSaver("message" + message.getId());
            String line;
            boolean sign = false;
            boolean sendTime = false;
            boolean content = false;
            boolean senderId = false;
            boolean previousBegin = false;
            
            while ((line = reader.readLine()) != null) {
                saver.println(line);
                
                // sign
                if (!sign) {
                    Matcher matcher = SIGN.matcher(line);
                    
                    if (matcher.find()) {
                        String group = matcher.group(1);
                        message.setSign(group);
                        sign = true;
                    }
                }
                
                // send time
                if (sign && !sendTime) {
                    Matcher matcher = SEND_TIME.matcher(line);
                    
                    if (matcher.find()) {
                        String group = matcher.group(1);
                        try {
                            message.setSendTime(
                                    ADVANCED_SEND_TIME_FORMAT.parse(group));
                            sendTime = true;
                        } catch (ParseException ex) {
                            logger.warn("Invalid message send time: " + group, ex);
                        }
                    }
                }
                
                // content
                if (sign && !content) {
                    Matcher matcher = CONTENT.matcher(line);
                    
                    if (matcher.find()) {
                        String group = HtmlEntitiesConverter.convert(
                                matcher.group(1));
                        message.setContent(group);
                        content = true;
                    }
                }
                
                // sender id
                if (sign && !senderId) {
                    Matcher matcher = SENDER_ID.matcher(line);
                    
                    if (matcher.find()) {
                        String group = matcher.group(1);
                        try {
                            message.getSender().setId(Integer.parseInt(group));
                        } catch (NumberFormatException ex) {
                            logger.warn("Invalid message sender id: " + group, ex);
                        }
                    }
                }
                
                // time and text of previous message
                if (sign && !previousBegin) {
                    Matcher matcher = PREVIOUS_BEGIN.matcher(line);
                    
                    if (matcher.find()) {
                        previousBegin = true;
                    }
                }
                if (previousBegin) {
                    Matcher matcher = PREVIOUS_MESSAGE.matcher(line);
                    
                    if (matcher.find()) {
                        String group = matcher.group(1);
                        try {
                            message.setTimeOfPrevious(
                                    TIME_OF_PREVIOUS_FORMAT.parse(group));
                        } catch (ParseException ex) {
                            logger.warn("Invalid time of previous message: " + group, ex);
                        }
                        
                        group = HtmlEntitiesConverter.convert(
                                matcher.group(2));
                        message.setTextOfPrevious(group);
                    }
                }
            }
            
        } catch (MalformedURLException ex) {
            logger.warn("Message URL is malformed: " + getMessageUrl(message), ex);
        } catch (IOException ex) {
            logger.error("IOException occured while advanced receive. Message possibly lost.", ex);
            throw ex;
        } finally {
            if (reader != null) reader.close();
            if (saver != null) saver.close();
        }
    }
    
}
